package com.yj.fileUpload;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.MD5;
import com.yj.common.Result;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.EscapedErrors;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 文件上传接口
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/upload")
@Controller
@Slf4j
public class UploadController {

    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1000));

    /**
     * 文件上传方式一
     *
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping
    @ResponseBody
    public static Result upload(@RequestParam("file") MultipartFile file) throws IOException {

        localUpload(file);
        return Result.success("上传成功！");
    }

    /**
     * 文件上传方式二
     *
     * @param request
     * @return
     * @throws IOException
     */
    @PostMapping("/test")
    @ResponseBody
    public Result test(MultipartHttpServletRequest request) throws IOException {
        MultipartFile file = request.getFile("file");
        localUpload(file);
        return Result.success("test");
    }

    /**
     * 文件上传方式三
     *
     * @param uploadEntity
     * @return
     * @throws IOException
     */
    @PostMapping("/test2")
    @ResponseBody
    public Result test2(UploadEntity uploadEntity) throws IOException {
        List<MultipartFile> files = uploadEntity.getFiles();
        if (CollectionUtil.isNotEmpty(files)) {
            for (MultipartFile file : files) {
                localUpload(file);
            }
        }
        return Result.success("test2");
    }

    private static void localUpload(MultipartFile file) throws IOException {
        if (file.isEmpty()) {
            throw new IOException("上传失败，请选择文件");
        }
        // 获取文件名
        String fileName = file.getOriginalFilename();
        // 默认文件上传路径

        String filePath = "D:/upload/";

        if (!Files.exists(Paths.get(filePath))) {

            // 判断路径是否存在，不存在则创建
            Files.createDirectories(Paths.get(filePath));
        }


        // 获取文件前缀
        String prefix = LocalDate.now().toString().replace(":", "-") + RandomUtil.randomNumbers(3);
        // 获取文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 重命名文件
        fileName = prefix + suffix;
        // 保存文件
        file.transferTo(new java.io.File(filePath + fileName));
    }


    private static List<BigFile> files = new ArrayList<>();


    /**
     * 存在问题：
     * 1. MultipartFile 对象只能处理一次对象，不能重复读取，如果需要重复读取，需要使用 InputStream 对象。
     *
     * @param bigFile
     * @return
     */
    @Deprecated
    @PostMapping("/uploadBig")
    @ResponseBody
    public Result uploadBig(BigFile bigFile) {
        String filename = bigFile.getFileName();
        String md5 = bigFile.getMd5();
        String filePath = "D:\\upload\\";
        filePath = filePath + md5 + "\\";


        // 判断路径是否存在，不存在则创建
        if (Files.notExists(Paths.get(filePath))) {
            try {
                // 判断路径是否存在，不存在则创建
                Files.createDirectories(Paths.get(filePath));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        //filePath = filePath + filename;
        Integer totalNumber = bigFile.getTotalNumber();

        files.add(bigFile);

        String finalFilePath = filePath;
        new Thread(() -> {
            for (BigFile file : files) {
                executor.execute(() -> {
                    doUpload(finalFilePath, file);
                });
            }
        }).start();
        // 上传文件
        // 保存文件
        // 返回结果
        return Result.success("上传成功！");
    }


    @SneakyThrows
    private void doUpload(String filePath, BigFile bigFile) {

        Integer chunkSize = bigFile.getChunkSize();
        Integer chunkNumber = bigFile.getChunkNumber();
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath + bigFile.getFileName(), "rw");
             InputStream inputStream = bigFile.getFile().getInputStream();) {
            // 定位到指定位置
            byte[] fileContent = new byte[chunkSize];
            inputStream.read(fileContent);
            randomAccessFile.seek(chunkNumber * chunkSize);
            randomAccessFile.write(fileContent);
        } catch (IOException e) {
            log.info("上传失败", e);
        }
    }

    @GetMapping("/fileUpload")
    public ModelAndView get() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("fileUpload");
        return modelAndView;
    }

    private static final String UPLOAD_PATH = "D:\\upload\\";

    @PostMapping("/chunk/upload")
    @ResponseBody
    public Result uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkNumber") Integer chunkNumber, @RequestParam("chunkSize") Integer chunkSize, @RequestParam("totalNumber") Integer totalNumber, @RequestParam("md5") String md5) throws IOException {

        // 文件存放地址
        String filePath = UPLOAD_PATH + md5 + "\\";
        // 分片上传标志
        String chuckFlag = UPLOAD_PATH + md5 + "\\" + "chuckFlag.conf";
        // 判断路径是否存在，不存在则创建
        if (Files.notExists(Paths.get(filePath))) {
            try {
                // 创建文件夹
                Files.createDirectories(Paths.get(filePath));

                if (Files.notExists(Paths.get(chuckFlag))){
                    // 创建分片上传标志文件
                    Files.createFile(Paths.get(chuckFlag));
                    // 初始化分片上传标志文件
                    byte[] flag = new byte[totalNumber];
                    Files.write(Paths.get(chuckFlag), flag);
                }

            } catch (IOException e) {
                log.error("创建文件夹失败", e);
                return Result.failed("上传失败！");
            }
        }
        String fileName = filePath + file.getOriginalFilename();
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(fileName, "rw");
             RandomAccessFile randomAccessChuckFlag = new RandomAccessFile(chuckFlag, "rw");
             InputStream inputStream = file.getInputStream()) {

            // 定位到指定位置
            int size = Math.min(inputStream.available(), chunkSize);

            byte[] fileContent = new byte[size];
            inputStream.read(fileContent);
            randomAccessFile.seek(chunkNumber * chunkSize);
            randomAccessFile.write(fileContent);
            // 更新分片上传标志文件
            randomAccessChuckFlag.seek(chunkNumber);
            randomAccessChuckFlag.write(1);
        } catch (Exception e) {
            log.error("上传失败", e);
            return Result.failed("上传失败！");
        }

        return Result.success("上传成功！");
    }


    @SneakyThrows
    @PostMapping("/checkFile")
    @ResponseBody
    public Result checkChunk(@RequestParam("md5") String md5) {

        String chuckFlag = UPLOAD_PATH + md5 + "\\" + "chuckFlag.conf";
        StringBuilder sb = new StringBuilder();

        // 获取分片信息
        byte[] bytes = Files.readAllBytes(Paths.get(chuckFlag));
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            sb.append(String.valueOf(b));
        }

        // 如果分片上传标志文件中存在0，则表示上传失败
        if (!sb.toString().contains("0")) {

            File file = new File(chuckFlag);
            File parent = new File(file.getParent());
            File[] files = parent.listFiles();

            // 获取分片下的上传文件
            for (File f : files) {
                if (!f.getName().contains(".conf")){
                    String absolutePath = f.getAbsolutePath();
                    try(InputStream inputStream = new FileInputStream(absolutePath)){
                        String fileMd5 = DigestUtils.md5DigestAsHex(inputStream);
                        if (!fileMd5.equals(md5)){
                            return Result.failed("上传失败！");
                        }
                    }
                }
            }
        }else{
            return Result.failed(sb.toString());
        }
        return Result.success(sb.toString());
    }
}
